/*
 Copyright (C) 2010 Kristian Duske

 This file is part of TrenchBroom.

 TrenchBroom is free software: you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation, either version 3 of the License, or
 (at your option) any later version.

 TrenchBroom is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with TrenchBroom. If not, see <http://www.gnu.org/licenses/>.
 */

#include "LayerNode.h"

#include "mdl/BrushNode.h"
#include "mdl/EntityNode.h"
#include "mdl/GroupNode.h"
#include "mdl/ModelUtils.h"
#include "mdl/PatchNode.h"
#include "mdl/TagVisitor.h"
#include "mdl/Validator.h"
#include "mdl/WorldNode.h"

#include "kd/contracts.h"
#include "kd/overload.h"

#include <algorithm>
#include <string>

namespace tb::mdl
{

LayerNode::LayerNode(Layer layer)
  : m_layer{std::move(layer)}
{
}

const Layer& LayerNode::layer() const
{
  return m_layer;
}

Layer LayerNode::setLayer(Layer layer)
{
  contract_pre(layer.defaultLayer() == m_layer.defaultLayer());

  using std::swap;
  swap(m_layer, layer);
  return layer;
}

bool LayerNode::isDefaultLayer() const
{
  return m_layer.defaultLayer();
}

void LayerNode::sortLayers(std::vector<LayerNode*>& layers)
{
  std::ranges::stable_sort(layers, [](const auto* a, const auto* b) {
    return a->layer().sortIndex() < b->layer().sortIndex();
  });
}

void LayerNode::sortLayers(std::vector<const LayerNode*>& layers)
{
  std::ranges::stable_sort(layers, [](const auto* a, const auto* b) {
    return a->layer().sortIndex() < b->layer().sortIndex();
  });
}

const std::optional<IdType>& LayerNode::persistentId() const
{
  return m_persistentId;
}

void LayerNode::setPersistentId(const IdType persistentId)
{
  m_persistentId = persistentId;
}

const std::string& LayerNode::doGetName() const
{
  return layer().name();
}

const vm::bbox3d& LayerNode::doGetLogicalBounds() const
{
  if (!m_boundsValid)
  {
    validateBounds();
  }
  return m_logicalBounds;
}

const vm::bbox3d& LayerNode::doGetPhysicalBounds() const
{
  if (!m_boundsValid)
  {
    validateBounds();
  }
  return m_physicalBounds;
}

double LayerNode::doGetProjectedArea(const vm::axis::type) const
{
  return static_cast<double>(0);
}

Node* LayerNode::doClone(const vm::bbox3d&) const
{
  auto result = std::make_unique<LayerNode>(m_layer);
  cloneAttributes(*result);
  return result.release();
}

bool LayerNode::doCanAddChild(const Node* child) const
{
  return child->accept(kdl::overload(
    [](const WorldNode*) { return false; },
    [](const LayerNode*) { return false; },
    [](const GroupNode*) { return true; },
    [](const EntityNode*) { return true; },
    [](const BrushNode*) { return true; },
    [](const PatchNode*) { return true; }));
}

bool LayerNode::doCanRemoveChild(const Node* /* child */) const
{
  return true;
}

bool LayerNode::doRemoveIfEmpty() const
{
  return false;
}

bool LayerNode::doShouldAddToSpacialIndex() const
{
  return false;
}

void LayerNode::doNodePhysicalBoundsDidChange()
{
  invalidateBounds();
}

bool LayerNode::doSelectable() const
{
  return false;
}

void LayerNode::doPick(const EditorContext&, const vm::ray3d& /* ray */, PickResult&) {}

void LayerNode::doFindNodesContaining(const vm::vec3d& point, std::vector<Node*>& result)
{
  for (auto* child : Node::children())
  {
    child->findNodesContaining(point, result);
  }
}

void LayerNode::doAccept(NodeVisitor& visitor)
{
  visitor.visit(this);
}

void LayerNode::doAccept(ConstNodeVisitor& visitor) const
{
  visitor.visit(this);
}

void LayerNode::invalidateBounds()
{
  m_boundsValid = false;
}

void LayerNode::validateBounds() const
{
  m_logicalBounds = computeLogicalBounds(children(), vm::bbox3d{0.0});
  m_physicalBounds = computePhysicalBounds(children(), vm::bbox3d{0.0});
  m_boundsValid = true;
}

void LayerNode::doAcceptTagVisitor(TagVisitor& visitor)
{
  visitor.visit(*this);
}

void LayerNode::doAcceptTagVisitor(ConstTagVisitor& visitor) const
{
  visitor.visit(*this);
}

} // namespace tb::mdl
